package admin.auth.config;


import admin.auth.domain.AuthenticationKeyGenerator;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;
import org.springframework.security.rsa.crypto.KeyStoreKeyFactory;
import admin.auth.component.JwtTokenEnhancer;
import admin.auth.feishu.properties.AuthProperties;
import admin.auth.service.impl.UserServiceImpl;
import admin.common.constant.AuthConstant;
import admin.common.enums.TokenStoreType;
import admin.common.properties.OauthProperties;

import javax.sql.DataSource;
import java.security.KeyPair;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

import static admin.common.enums.TokenStoreType.*;

/**
 * 认证服务器配置
 * Created by zgb on 2020/6/19.
 */
@Data
@AllArgsConstructor
@Configuration
@EnableAuthorizationServer
public class Oauth2ServerConfig extends AuthorizationServerConfigurerAdapter {


    @Autowired
    private UserServiceImpl userDetailsService;
    //认证
    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private JwtTokenEnhancer jwtTokenEnhancer;


    @Autowired
    private OauthProperties oauthProperties;


    @Autowired
    private AuthProperties authProperties;


    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private RedisConnectionFactory redisConnectionFactory;

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        //配置client相关信息的处理方式为jdbc模式,支持动态修改
        clients.withClientDetails(jdbcClientDetailsService());
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
        List<TokenEnhancer> delegates = new ArrayList<>();
        delegates.add(jwtTokenEnhancer);
        delegates.add(accessTokenConverter());
        enhancerChain.setTokenEnhancers(delegates); //配置JWT的内容增强器
        endpoints.authenticationManager(authenticationManager)
                .userDetailsService(userDetailsService) //配置加载用户信息的服务
                .accessTokenConverter(accessTokenConverter())
                .tokenEnhancer(enhancerChain);


        endpoints.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST,
                HttpMethod.OPTIONS, HttpMethod.PUT, HttpMethod.PATCH, HttpMethod.DELETE);
        //配置token的处理方式为jdbc模式
        endpoints.tokenStore(tokenStore());
        TokenStoreType tokenStoreType = TokenStoreType.get(authProperties.getTokenStoreType());
        if(Objects.equals(REDIS,tokenStoreType)){
            //配置TokenService参数
            DefaultTokenServices tokenService = new DefaultTokenServices();
            tokenService.setTokenStore(endpoints.getTokenStore());
            tokenService.setSupportRefreshToken(true);
            tokenService.setClientDetailsService(endpoints.getClientDetailsService());
            tokenService.setTokenEnhancer(endpoints.getTokenEnhancer());
            //token有效期 1小时
            tokenService.setAccessTokenValiditySeconds(authProperties.getAccessTokenValiditySeconds());
            //token刷新有效期 15天
            tokenService.setRefreshTokenValiditySeconds(authProperties.getRefreshTokenValiditySeconds());
            tokenService.setReuseRefreshToken(false);
            endpoints.tokenServices(tokenService);
        }
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        /*
        /oauth/token
                这个如果配置支持allowFormAuthenticationForClients的，且url中有client_id和client_secret的会走ClientCredentialsTokenEndpointFilter来保护
                如果没有支持allowFormAuthenticationForClients或者有支持但是url中没有client_id和client_secret的，走basic认证保护
        */
        security
                .tokenKeyAccess("permitAll()")
                .checkTokenAccess("permitAll()")
                .allowFormAuthenticationForClients();
        security.allowFormAuthenticationForClients();
        security.passwordEncoder(passwordEncoder); //设置 客户信息 client_secret  使用明文验证
    }

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
        jwtAccessTokenConverter.setKeyPair(keyPair());
        return jwtAccessTokenConverter;
    }

    /**
     * jwt用到RSA算法
     *
     * @return
     */
    @Bean
    public KeyPair keyPair() {
        //从classpath下的证书中获取秘钥对
        KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("jwt.jks"), "123456".toCharArray());
        return keyStoreKeyFactory.getKeyPair("jwt", "123456".toCharArray());
    }


    /**
     * 配置自定义数据源，覆盖spring security oauth2自带的
     *
     * @return
     */
    @Bean
    @Primary //有多个配置实现时指定要使用的配置
    @ConfigurationProperties(prefix = "spring.datasource") //指定自定义数据源
    public DataSource dataSource() {
        return DataSourceBuilder.create().build();
    }

    /**
     * token存储
     *
     * @return
     */
    @Bean
    public TokenStore tokenStore() {
        TokenStore tokenStore = null;
        TokenStoreType tokenStoreType = TokenStoreType.get(authProperties.getTokenStoreType());
        if (tokenStoreType==null) {
            //默认
            tokenStore = new JwtTokenStore(accessTokenConverter() );
        } else {
            if(Objects.equals(JWT,tokenStoreType)){
                //默认
                tokenStore = new JwtTokenStore(accessTokenConverter() );
            }else if(Objects.equals(REDIS,tokenStoreType)){
                tokenStore = new RedisTokenStore(redisConnectionFactory);
                //设置每次请求都生成新token
                ((RedisTokenStore) tokenStore).setAuthenticationKeyGenerator(new AuthenticationKeyGenerator());
                // 设置key的层级前缀，方便查询
                ((RedisTokenStore) tokenStore).setPrefix(AuthConstant.TOKEN_STORE_PREFIX);
            }else if(Objects.equals(JDBC,tokenStoreType)){
                tokenStore = new JdbcTokenStore(dataSource());
            }else{
                //默认
                tokenStore = new JwtTokenStore(accessTokenConverter() );
            }
        }

        return tokenStore;
    }


    /**
     * client配置存储
     *
     * @return
     */
    @Bean
    public ClientDetailsService jdbcClientDetailsService() {
        return new JdbcClientDetailsService(dataSource());
    }


}
